home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 5
/
Skunkware 5.iso
/
src
/
X11
/
xarchie-2.0.9
/
ftp.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-06-18
|
47KB
|
1,680 lines
/*
* ftp.c : This code implements an asynchronous ftp client.
*
* George Ferguson, ferguson@cs.rochester.edu, 23 Apr 1993.
*
* The client is represented by an instance of the FtpContext structure,
* which includes the state of the client with respect to a finite-state
* model of the FTP process. All aspects of the connection are contained
* in the structure, so a process can have multiple outstanding FTP
* sessions (subject to filesystem limitations, of course).
*
* Refer to RFC959 for details of the state model.
*
* Two external functions must be provided:
* void RegisterFtpFd( ftpc, fd, flags, func )
* Should arrange to call FUNC passing FTPC whenever FD is ready
* as indicated by FLAGS (from <fcntl.h>).
* void UnregisterFtpFd( ftpc, fd )
* Cancels a previous registration of FD for FTPC.
* Samples of these using select(2) are given below in the STANDALONE
* section. Xarchie uses the XtAppAddInput() mechanism. The code also
* uses alert0() and status0() for messages, sysError() for error
* messages from system calls, and ftpPrompt() if prompting is enabled.
*
* To use these routines:
* ftpc = ftpNewContext(...);
* ftpStart(ftpc);
* The function you registered as the "done" parameter in ftpNewContext
* will be called back when the process finishes (for any reason). You
* should call ftpFreeContext() in this callback (or register it as the
* callback, or register NULL).
*
* This code is decidely *not* a general-purpose FTP library. It is
* designed to support the batch transfers required by xarchie. I suppose
* that it could be modified to be more general without too much effort.
* In particular, many of the routines called by ftpProcessReply could
* be made into external interface functions. Volunteers?
*
* 28 Apr 1993: Status mesage for bytes transferred was backwards.
* 1 Jun 1993: Fixes for ISC. Are these needed for other SYS5?
* 30 Jun 1993: Fixes for Encore Umax (used cpp symbol "umax" as a guess).
* 24 Aug 1993: Allow 200 reply for CWD as well as 250.
* Non-blocking IO defs for NeXT.
*/
#include <stdio.h>
#include <errno.h>
#ifndef MSDOS
extern int errno; /* Encore needs this despite <errno.h> */
#endif
#include "config.h"
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include "ftp.h" /* includes <sys/types.h> and <netinet/in.h> */
#include "sysdefs.h" /* char *malloc() */
#include "stringdefs.h" /* char *strcpy(), *index(); */
#include "debug.h" /* MSG[0-3]() macros */
/* These functions have to be provided: */
extern void RegisterFtpFd(/* ftpc, fd, O_RDWR | O_RDONLY, func */);
extern void UnregisterFtpFd(/* ftpc, fd */);
extern int ftpPrompt(/* ftpc */);
extern void status0(/* str */);
extern void alert0(/* str */);
extern void sysError(/* str */);
/*
* Portable non-blocking I/O macros, I hope.
* hp300 : From Andy.Linton@comp.vuw.ac.nz
* sgi : From amoss@cs.huji.ac.il
* umax : From Gerry.Tomlinson@newcastle.ac.uk
* NeXT : From jk@zarniwoop.pc-labor.uni-bremen.de
*/
#if defined(hp300) || defined(NeXT)
/* Here's for BSD, maybe, but Sys5 can't tell this from EOF. */
# include <fcntl.h>
# define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,O_NDELAY)
# define ITWOULDBLOCK EWOULDBLOCK
#else
#if defined(sgi)
# include <fcntl.h>
# define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,O_NONBLOCK)
# define ITWOULDBLOCK EWOULDBLOCK
#else
#if defined(umax)
# define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,FNDELAY)
# define ITWOULDBLOCK EWOULDBLOCK
#else
/* This is POSIX, the default, which uses EAGAIN. */
# include <fcntl.h>
# define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,O_NONBLOCK)
# define ITWOULDBLOCK EAGAIN
#endif /* umax */
#endif /* sgi */
#endif /* hp300 */
/*
* Networking includes from Brendan Kehoe (dirsend.c)
*/
#include <netdb.h>
#include <sys/socket.h>
#ifndef hpux
# include <arpa/inet.h>
#endif
/* Interactive UNIX keeps some of the socket definitions in funny places. */
#ifdef ISC
# include <net/errno.h> /* I wonder about this... */
#endif /* ISC */
#ifndef IPPORT_FTP
#define IPPORT_FTP 21 /* From <netinet/in.h> */
#endif
#define NewString(S) strcpy(malloc(strlen(S)+1),S)
#define ftpfilecmdstr(X) ((X)==FTP_GET ? "GET" : "PUT")
/*
* Functions defined here:
*/
FtpContext *ftpNewContext();
void ftpStart(),ftpAbort();
void ftpFreeContext();
/* Initialization */
static int ftpGetHostAndPort();
/* Connection establishment */
static void ftpConnectCallback();
static int ftpGetSocket(),ftpConnect();
/* Reading server replies */
static void ftpInitReply(),ftpReplyCallback();
/* Main FTP client state-machine */
static void ftpProcessReply();
static void ftpAlert(),ftpHandleErrorThenQuit(),ftpDone();
static void ftpSendType(),ftpSendNextCwd();
static int ftpSendPort();
static void ftpSendGetPut();
static int ftpAcceptDataConn(),ftpSetupLocalData();
/* Data connection processing */
static void ftpDataCallback();
static void ftpReadData(),ftpWriteData(),ftpCleanupDataConn();
/* Sending commands to the server */
static void ftpStartSendingCmd(),ftpWriteCallback();
static void ftpSendCmd(),ftpSendAbort();
#ifdef DEBUG
char *ftpstatestr();
#endif
/* - - - - - - - - */
/* Exported functions: */
/*
* Returns a pointer to a new FtpContext for which the connection is
* started. The external function RegisterFtpFd() is used to register
* the control socket for subsequent processing by ftpProcess().
*/
FtpContext *
ftpNewContext(hostname,user,pass,cwd,local_dir,type,stripcr,prompt,
filecmd,files,num_files,trace,done1,done)
char *hostname,*user,*pass,*cwd,*local_dir;
int type,stripcr,prompt,filecmd;
char **files;
int num_files;
FtpTraceProc trace;
FtpCallbackProc done1,done;
{
FtpContext *ftpc;
int i;
/* Initialize the data structure */
if ((ftpc=(FtpContext *)malloc(sizeof(FtpContext))) == NULL) {
fprintf(stderr,"ftpNewContext: malloc failed\n");
return(NULL);
}
/* User-specified stuff */
ftpc->hostname = NewString(hostname);
ftpc->user = NewString(user);
ftpc->pass = NewString(pass);
ftpc->cwd = NewString(cwd);
ftpc->wd = ftpc->cwd;
ftpc->local_dir = NewString(local_dir);
ftpc->type = type;
ftpc->stripcr = stripcr;
ftpc->prompt = prompt;
ftpc->filecmd = filecmd;
ftpc->num_files = num_files;
ftpc->files = (char **)calloc(num_files,sizeof(char *));
for (i=0; i < num_files; i++)
ftpc->files[i] = NewString(files[i]);
ftpc->this_file = -1; /* preincrement later */
ftpc->this_size = 0;
ftpc->trace = trace;
ftpc->done1 = done1;
ftpc->done = done;
/* Default stuff */
ftpc->reply[0] = '\0';
ftpc->stripcr = 1;
/* Undefined stuff */
ftpc->ctrl = ftpc->data = ftpc->port = ftpc->local_fd = -1;
ftpc->state = ftpc->iostate = ftpc->reply_state = ftpc->saved_state = 0;
ftpc->retcode = ftpc->tmpcode = 0;
ftpc->cmd = NULL;
/* Go start the connection */
if (ftpGetHostAndPort(ftpc) < 0) {
ftpFreeContext(ftpc);
return(NULL);
}
ftpc->state = 0;
return(ftpc);
}
void
ftpFreeContext(ftpc)
FtpContext *ftpc;
{
int i;
DEBUG1("ftpFreeContext: ftpc=0x%x\n",ftpc);
if (ftpc->ctrl != -1) {
UnregisterFtpFd(ftpc,ftpc->ctrl);
DEBUG1("ftpFreeContext: closing ctrl %d\n",ftpc->ctrl);
close(ftpc->ctrl);
}
if (ftpc->data != -1) {
UnregisterFtpFd(ftpc,ftpc->data);
DEBUG1("ftpFreeContext: closing data %d\n",ftpc->data);
close(ftpc->data);
}
if (ftpc->port != -1) {
DEBUG1("ftpFreeContext: closing port %d\n",ftpc->port);
close(ftpc->port);
}
if (ftpc->local_fd != -1) {
DEBUG1("ftpFreeContext: closing local_fd %d\n",ftpc->local_fd);
close(ftpc->local_fd);
}
if (ftpc->hostname)
free(ftpc->hostname);
if (ftpc->user)
free(ftpc->user);
if (ftpc->pass)
free(ftpc->pass);
if (ftpc->cwd)
free(ftpc->cwd);
if (ftpc->local_dir)
free(ftpc->local_dir);
if (ftpc->files) {
for (i=0; i < ftpc->num_files; i++)
if (ftpc->files[i]) free(ftpc->files[i]);
free((char *)(ftpc->files));
}
if (ftpc->h_addr_list)
free((char *)(ftpc->h_addr_list));
DEBUG0("ftpFreeContext: done\n");
}
void
ftpStart(ftpc)
FtpContext *ftpc;
{
DEBUG1("ftpStart: ftpc=0x%x\n",ftpc);
ftpc->state = FTPS_OPEN;
ftpConnectCallback(ftpc); /* get things started */
DEBUG0("ftpStart: done\n");
}
void
ftpAbort(ftpc)
FtpContext *ftpc;
{
DEBUG1("ftpAbort: ftpc=0x%x\n",ftpc);
/*
* If we're aborting before any files transferred or after an
* abort or quit, just close down the whole connection.
*/
if (ftpc->state < FTPS_READY || ftpc->state > FTPS_EOF) {
ftpDone(ftpc);
return;
}
/*
* Close the dataconn so we don't get swamped in select() while
* waiting for the response to the abort on the ctrlconn.
*/
if (ftpc->data != -1) {
UnregisterFtpFd(ftpc,ftpc->data);
}
/* Then send the abort sequence */
ftpSendAbort(ftpc);
DEBUG0("ftpAbort: done\n");
}
/* - - - - - - - - */
/* Initialization: */
/*
* This function is called from ftpNewContext() to initialize the host
* address information in the FTPC.
*/
static int
ftpGetHostAndPort(ftpc)
FtpContext *ftpc;
{
struct servent *serv;
struct hostent *host;
unsigned long hostaddr;
char msg[256];
int i;
DEBUG2("ftpGetHostAndPort: ftpc=0x%x: \"%s\"\n",ftpc,ftpc->hostname);
/*
* Get ftp port
*/
DEBUG0("ftpGetHostAndPort: getting ftp service port\n");
serv = getservbyname("ftp","tcp");
/* UCX needs 0 or -1 */
if (serv == (struct servent *)0 || serv == (struct servent *)-1) {
alert0("Can't find service 'ftp/tcp' in list of services -- using default port");
ftpc->servport = htons((unsigned short)IPPORT_FTP);
} else {
ftpc->servport = (unsigned short)serv->s_port;
}
DEBUG1("ftpGetHostAndPort: ftp service port is %d\n",ftpc->servport);
/*
* Get host address
*/
sprintf(msg,"Getting address for host \"%.200s\"",ftpc->hostname);
status0(msg);
if ((host=gethostbyname(ftpc->hostname)) == NULL) {
DEBUG0("ftpGetHostAndPort: gethostbyname failed, trying inet_addr()\n");
/*
* If gethostbyname fails, then maybe we've been given an IP
* address directly. Let's see.
*/
hostaddr = inet_addr(ftpc->hostname);
if (hostaddr == (unsigned long)-1) {
/*
* Nope - complete failure.
*/
sprintf(msg,"Can't find address of host \"%.200s\"",
ftpc->hostname);
alert0(msg);
return(-1);
} else {
/*
* inet_addr succeeded, so we make a dummy array of hostaddrs.
*/
ftpc->h_addr_list = (char **)calloc(2,sizeof(char *));
bcopy((char *)&hostaddr,ftpc->h_addr_list[0],sizeof(char *));
hostaddr = (unsigned long)0;
bcopy((char *)&hostaddr,ftpc->h_addr_list[1],sizeof(char *));
ftpc->this_addr = ftpc->h_addr_list-1; /* preincrement later */
DEBUG1("ftpGetHostAndPort: inet_addr returned %s\n",
inet_ntoa(*(struct in_addr*)(ftpc->h_addr_list[0])));
return(0);
}
} else {
/*
* If gethostbyname succeeeds, it fills in a list of addresses.
* We'll copy them into the ftpc.
*/
for (i=0; host->h_addr_list[i]; i++)
/*EMPTY*/;
DEBUG1("ftpGetHostAndPort: gethostbynname returned %d addr(s)\n",i);
ftpc->h_addr_list = (char **)calloc(i+1,sizeof(char *));
for (i=0; host->h_addr_list[i]; i++) {
bcopy(host->h_addr_list[i],
(char *)(ftpc->h_addr_list+i),sizeof(char *));
DEBUG2("ftpGetHostAndPort: h_addr_list[%d] = %s\n",i,
inet_ntoa(*(struct in_addr*)(ftpc->h_addr_list+i)));
}
bzero((char *)(ftpc->h_addr_list+i),sizeof(char *));
ftpc->this_addr = ftpc->h_addr_list-1; /* preincrement later */
return(0);
}
}
/* - - - - - - - - */
/* Connection establishment: */
/*
* This function goes through the list of addresses trying to connect to
* the server. It is initially called from ftpNewContext(), and subsequently
* whenever the ctrl socket it ready for writing (indicating connect()
* completed, possibly with an error).
*/
static void
ftpConnectCallback(ftpc)
FtpContext *ftpc;
{
int retcode;
char msg[256];
DEBUG2("ftpConnectCallback: ftpc=0x%x, state=%s\n",
ftpc,ftpstatestr(ftpc->state));
redo:
switch (ftpc->state) {
case FTPS_OPEN:
ftpc->this_addr += 1; /* preincrement */
DEBUG1("ftpConnectCallback: OPEN: %s\n",
inet_ntoa(*(struct in_addr*)(ftpc->this_addr)));
if (*(ftpc->this_addr)) { /* Not at last host */
if (ftpGetSocket(ftpc) < 0) {
ftpDone(ftpc);
} else {
/* Arrange to get called back if connect() would block */
RegisterFtpFd(ftpc,ftpc->ctrl,O_RDWR,ftpConnectCallback);
ftpc->state = FTPS_CONNECT;
goto redo;
}
} else { /* All hosts failed */
sprintf(msg,"Couldn't connect to host \"%s\"",ftpc->hostname);
alert0(msg);
ftpDone(ftpc);
}
break;
case FTPS_CONNECT:
/* We previously registered either above or below */
UnregisterFtpFd(ftpc,ftpc->ctrl);
retcode = ftpConnect(ftpc);
if (retcode < 0) {
DEBUG1("ftpConnectCallback: ftpConnect failed, closing ctrl %d\n",
ftpc->ctrl);
close(ftpc->ctrl);
ftpc->ctrl = -1;
ftpc->state = FTPS_OPEN; /* try next one */
goto redo;
} else if (retcode == 0) {
DEBUG0("ftpConnectCallback: ftpConnect would block\n");
/* Arrange to get called back when connect() won't block */
RegisterFtpFd(ftpc,ftpc->ctrl,O_RDWR,ftpConnectCallback);
ftpc->state = FTPS_CONNECT; /* try again when ready */
} else {
DEBUG0("ftpConnectCallback: ftpConnect ok\n");
ftpInitReply(ftpc); /* ok... */
ftpc->state = FTPS_CONNECTED; /* move on */
}
break;
default:
fprintf(stderr,"ftpConnectCallback: unknown state %d\n",ftpc->state);
abort();
}
DEBUG0("ftpConnectCallback: done\n");
}
/* - - - - - - - - */
/* Functions called by ftpConnectCallback(): */
/*
* This function opens a new socket for the ctrl connection, and sets
* it up for non-blocking IO. Everything is setup for later connect().
*/
static int
ftpGetSocket(ftpc)
FtpContext *ftpc;
{
DEBUG1("ftpGetSocket: addr=%s\n",
inet_ntoa(*(struct in_addr*)(ftpc->this_addr)));
/* Get a socket */
if ((ftpc->ctrl=socket(AF_INET,SOCK_STREAM,0)) < 0) {
sysError("socket(ftpGetSocket)");
return(-1);
}
DEBUG1("ftpGetSocket: socket() returned %d\n",ftpc->ctrl);
/*
* According to jbh@moses.aii.COM, we can't do this until we connect
* on (at least) ISC variants of SYS5. I have not had problems with
* other SYS5 systems, including HPUX. When ISC is defined, we do it
* in ftpConnect(), although we may then block in connect().
*/
#ifndef ISC
/* Setup socket for async i/o */
MAKE_NONBLOCKING(ftpc->ctrl);
#endif
/* Set up the address spec. */
bzero((char *)&(ftpc->saddr),sizeof(struct sockaddr_in));
ftpc->saddr.sin_family = AF_INET;
ftpc->saddr.sin_port = ftpc->servport;
ftpc->saddr.sin_addr = *((struct in_addr *)(ftpc->this_addr));
/* Ready to connect() */
DEBUG0("ftpGetSocket: done\n");
return(0);
}
/*
* Calls connect(). Returns -1 if this host is botched, 0 if connect()
* would have blocked, or 1 if we should proceed.
*/
static int
ftpConnect(ftpc)
FtpContext *ftpc;
{
int retcode,addrlen;
char msg[256];
sprintf(msg,"Connecting to %.200s (%s)",
ftpc->hostname,inet_ntoa(*(struct in_addr*)(ftpc->this_addr)));
status0(msg);
DEBUG0("ftpConnect: calling connect()...\n");
retcode = connect(ftpc->ctrl,(struct sockaddr *)&(ftpc->saddr),
sizeof(struct sockaddr_in));
if (retcode < 0 && errno == EINPROGRESS) {
DEBUG0("ftpConnect: connect() EINPROGRESS\n");
return(0);
} else if (retcode < 0 && errno != EISCONN) {
#ifdef DEBUG
perror("ftpConnect: connect()");
#endif
return(-1);
}
#ifdef DEBUG
else if (retcode < 0 && errno == EISCONN)
fprintf(stderr,"ftpConnect: connect() EISCONN\n");
else
fprintf(stderr,"ftpConnect: connect() returned ok\n");
#endif
/* According to jbh@moses.aii.COM, see comments in ftpGetSocket(). */
#ifdef ISC
MAKE_NONBLOCKING(ftpc->ctrl); /* Setup socket for async i/o */
#endif
/* Get name (address) of socket */
addrlen = sizeof(struct sockaddr_in);
retcode =
getsockname(ftpc->ctrl,(struct sockaddr *)&(ftpc->saddr),&addrlen);
if (retcode < 0) {
sysError("getsockname(ftpConnect)");
return(-1);
}
DEBUG1("ftpConnect: getsockname() returned %s\n",
inet_ntoa(ftpc->saddr.sin_addr));
return(1);
}
/* - - - - - - - - */
/* Reading server replies: */
/*
* This function must be called to initialize the reply in the FTPC and
* to ensure that the appropriate callback is registered to read it.
*/
static void
ftpInitReply(ftpc)
FtpContext *ftpc;
{
DEBUG1("ftpInitReply: ftpc=0x%lx\n",ftpc);
ftpc->retcode = 0;
ftpc->reply_len = 0;
*(ftpc->reply) = '\0';
ftpc->reply_state = FTPS_REPLY_CODE;
/* We're expecting a reply, so register ctrl for reading only */
RegisterFtpFd(ftpc,ftpc->ctrl,O_RDONLY,ftpReplyCallback);
DEBUG0("ftpInitReply: done\n");
}
/*
* This function is the low-level reply parser. It reads and
* parses reply messages from the server, handles TELNET commands,
* and translate codes in the reply prefix into integers. It stores the
* reply code in ftpc->retcode and puts the text of the reply in ftpc->reply.
* It is called whenever the ctrlconn is ready for reading and we are
* expecting a reply (ie., after ftpInitReply()). Once the reply is
* complete, we call ftpProcessReply().
*/
static void
ftpReplyCallback(ftpc)
FtpContext *ftpc;
{
int n;
unsigned char c,cc;
for (;;) {
/*
DEBUG2("ftpReplyCallback: ftpc=0x%x, reply_state=%s\n",
ftpc,ftpstatestr(ftpc->reply_state));
*/
n = read(ftpc->ctrl,&c,1); /* Read a byte */
if (n < 0 && errno != ITWOULDBLOCK) { /* Error */
sysError("read(ftpReplyCallback)");
DEBUG0("ftpReplyCallback: error reading ctrlconn\n");
ftpDone(ftpc);
return;
} else if (n < 0 && errno == ITWOULDBLOCK) { /* Would block */
/*DEBUG0("ftpReplyCallback: ctrlconn would block\n");*/
return;
} else if (n == 0) { /* EOF */
*(ftpc->reply+ftpc->reply_len) = '\0';
/* Could check against current retcode, if any... */
if (ftpc->state == FTPS_QUIT)
ftpc->retcode = FTP_SERVICE_CLOSING;
else
ftpc->retcode = FTP_SERVICE_UNAVAILABLE;
DEBUG1("ftpReplyCallback: EOF: retcode = %d\n",ftpc->retcode);
/* Reply is done, go process it */
break;
}
/*
* Otherwise, we got something, process it.
*/
if (c == IAC) { /* Telnet IAC */
ftpc->saved_state = ftpc->reply_state;
ftpc->reply_state = FTPS_REPLY_IAC1;
DEBUG1("ftpReplyCallback: IAC (saved_state=%d)\n",ftpc->saved_state);
continue;
} else if (c == '\r') { /* Skip <CR>, is this ok? */
continue;
}
/*
* We got something that's not IAC, process it.
*/
/*DEBUG2("ftpReplyCallback: `%c' (%d)\n",c,c);*/
switch (ftpc->reply_state) {
case FTPS_REPLY_IAC1:
DEBUG1("ftpReplyCallback: IAC %c",c);
ftpc->reply_state = FTPS_REPLY_IAC2;
break;
case FTPS_REPLY_IAC2:
switch (c) {
case WILL:
case WONT: c = DONT;
break;
case DO:
case DONT: c = WONT;
break;
default:
DEBUG0(" (ignored)\n");
continue;
}
DEBUG1(", reply %c\n",c);
cc = IAC;
write(ftpc->ctrl,&cc,1);
write(ftpc->ctrl,&c,1);
ftpc->reply_state = FTPS_REPLY_IAC3;
break;
case FTPS_REPLY_IAC3:
write(ftpc->ctrl,&c,1);
ftpc->reply_state = ftpc->saved_state;
DEBUG1("ftpReplyCallback: IAC done, restored state = %d\n",
ftpc->reply_state);
break;
case FTPS_REPLY_CODE:
if (c < '0' || c > '9') {
*(ftpc->reply+ftpc->reply_len) = '\0';
DEBUG1("ftpReplyCallback: CODE: retcode = %d\n",ftpc->retcode);
goto done;
}
*(ftpc->reply+ftpc->reply_len++) = c;
ftpc->retcode = ftpc->retcode * 10 + c - '0';
/*DEBUG1("ftpReplyCallback: retcode now %d\n",ftpc->retcode);*/
if (ftpc->retcode >= 100)
ftpc->reply_state = FTPS_REPLY_CONT;
break;
case FTPS_REPLY_CONT:
/* we reach here after we finished reading the code or when we
* struck a line beginning with at least three digits, check if
* this is the last line of the reply
*/
*(ftpc->reply+ftpc->reply_len++) = c;
if (c == '-') {
/*
DEBUG0("ftpReplyCallback: continuation\n");
*/
ftpc->reply_state = FTPS_REPLY_MORE;
} else {
/*
DEBUG0("ftpReplyCallback: final line\n");
*/
ftpc->reply_state = FTPS_REPLY_LAST;
}
break;
case FTPS_REPLY_LAST:
if (c == '\n') {
*(ftpc->reply+ftpc->reply_len) = '\0';
/*
DEBUG1("ftpReplyCallback: LAST: retcode = %d\n",ftpc->retcode);
*/
goto done;
} else {
*(ftpc->reply+ftpc->reply_len++) = c;
/*
DEBUG3("ftpReplyCallback: LAST: %03d: \"%.*s\"\n",
ftpc->reply_len,ftpc->reply_len,ftpc->reply);
*/
}
break;
case FTPS_REPLY_MORE:
*(ftpc->reply+ftpc->reply_len++) = c;
/*
DEBUG3("ftpReplyCallback: MORE: %03d: \"%.*s\"\n",
ftpc->reply_len,ftpc->reply_len,ftpc->reply);
*/
if (c == '\n') {
ftpc->tmpcode = 0;
ftpc->reply_state = FTPS_REPLY_CHCK;
}
break;
case FTPS_REPLY_CHCK:
if (c < '0' || c > '9') {
*(ftpc->reply+ftpc->reply_len++) = c;
/*
DEBUG3("ftpReplyCallback: CHCK: %03d: \"%.*s\"\n",
ftpc->reply_len,ftpc->reply_len,ftpc->reply);
*/
ftpc->reply_state = FTPS_REPLY_MORE;
} else {
ftpc->tmpcode = ftpc->tmpcode * 10 + c - '0';
if (ftpc->tmpcode >= 100) {
if (ftpc->tmpcode != ftpc->retcode)
ftpc->reply_state = FTPS_REPLY_MORE;
else
ftpc->reply_state = FTPS_REPLY_CONT;
}
}
break;
default:
fprintf(stderr,"ftpReplyCallback: unknown reply_state %d\n",
ftpc->reply_state);
abort();
}
}
/* if we get here, the reply is complete */
done:
UnregisterFtpFd(ftpc,ftpc->ctrl);
if (ftpc->trace) {
(*(ftpc->trace))(ftpc,0,ftpc->reply);
}
ftpProcessReply(ftpc);
}
/* - - - - - - - - */
/*
* This function implements the high-level FTP protocol using the state
* field of the FTPC. It is called from ftpReplyCallback() once we've read
* a reply and need to process it.
*/
static void
ftpProcessReply(ftpc)
FtpContext *ftpc;
{
char cmd[256];
DEBUG3("ftpProcessReply: ftpc=0x%x, state=%s, retcode=%d\n",
ftpc,ftpstatestr(ftpc->state),ftpc->retcode);
redo:
switch (ftpc->state) {
case FTPS_CONNECTED:
if (ftpc->retcode == FTP_SERVICE_RDY_TIME) { /* delay NNN minutes */
DEBUG0("ftpProcessReply: server not ready\n");
ftpAlert(ftpc);
} else if (ftpc->retcode == FTP_SERVICE_RDY_USER) {/* ready for USER */
sprintf(cmd,"USER %s",ftpc->user);
status0(cmd);
ftpc->state = FTPS_USER;
ftpSendCmd(ftpc,cmd);
} else { /* FTP_SERVICE_UNAVAILABLE */
ftpAlert(ftpc);
ftpDone(ftpc);
}
break;
case FTPS_USER:
if (ftpc->retcode == FTP_LOGIN_OK) { /* USER ok, no PASS needed */
ftpc->retcode = FTP_FILE_ACTION_OK;
ftpc->state = FTPS_CWD;
goto redo;
} else if (ftpc->retcode == FTP_LOGIN_NEED_PASSWD) { /* USER ok */
sprintf(cmd,"PASS %s",ftpc->pass); /* need PASS */
status0(cmd);
ftpc->state = FTPS_PASS;
ftpSendCmd(ftpc,cmd);
} else { /* ACCT needed or error */
ftpHandleErrorThenQuit(ftpc);
}
break;
case FTPS_PASS:
if (ftpc->retcode == FTP_LOGIN_OK) { /* PASS ok, ready to go */
ftpc->retcode = FTP_FILE_ACTION_OK;
ftpc->state = FTPS_CWD;
goto redo;
} else { /* ACCT needed or error */
ftpHandleErrorThenQuit(ftpc);
}
break;
case FTPS_CWD:
/* can come here direct from USER or PASS also... */
if (ftpc->retcode == FTP_FILE_ACTION_OK ||
ftpc->retcode == FTP_COMMAND_OK) { /* last CWD ok */
if (ftpc->wd == NULL || *(ftpc->wd) == '\0') { /* CWD done */
ftpc->state = FTPS_TYPE;
ftpSendType(ftpc);
} else { /* send next part of CWD */
ftpc->state = FTPS_CWD;
ftpSendNextCwd(ftpc);
}
} else { /* last CWD failed */
ftpHandleErrorThenQuit(ftpc);
}
break;
case FTPS_TYPE:
if (ftpc->retcode == FTP_COMMAND_OK) { /* TYPE ok */
ftpc->retcode = FTP_FILE_ACTION_OK;
ftpc->state = FTPS_READY;
goto redo;
} else { /* TYPE failed */
ftpHandleErrorThenQuit(ftpc);
}
break;
case FTPS_READY:
/* can get here from TYPE or EOF */
ftpc->this_file += 1;
if (ftpc->this_file < ftpc->num_files) { /* files left to transfer */
sprintf(cmd,"File %s?",ftpc->files[ftpc->this_file]);
status0(cmd);
if (ftpc->prompt && !ftpPrompt(ftpc)) {
goto redo;
}
if (ftpSendPort(ftpc) == 0) { /* PORT ok locally */
ftpc->state = FTPS_PORT;
} else { /* PORT failed locally */
ftpc->state = FTPS_QUIT; /* bag the whole thing */
ftpSendCmd(ftpc,"QUIT");
}
} else { /* no files left to transfer */
DEBUG0("ftpProcessReply: no more files\n");
ftpc->state = FTPS_QUIT;
ftpSendCmd(ftpc,"QUIT");
}
break;
case FTPS_PORT:
if (ftpc->retcode == FTP_COMMAND_OK) { /* PORT command ok */
ftpc->state = FTPS_GETPUT;
ftpSendGetPut(ftpc);
} else { /* PORT failed */
if (ftpc->port >= 0) {
DEBUG1("ftpProcessReply: closing port %d\n",ftpc->port);
close(ftpc->port);
ftpc->port = -1;
}
ftpHandleErrorThenQuit(ftpc);
}
break;
case FTPS_GETPUT:
if (FTP_REPLY_PRELIM(ftpc->retcode)) { /* dataconn ready */
status0(ftpc->reply+4);
if (sscanf(ftpc->reply,"%*[^(](%d bytes)",&(ftpc->this_size)) != 1)
ftpc->this_size = 0;
if (ftpAcceptDataConn(ftpc) < 0) { /* local failure */
if (ftpc->port != -1) { /* give up */
DEBUG1("ftpProcessReply: closing port %d\n",ftpc->port);
close(ftpc->port);
ftpc->port = -1;
}
ftpc->state = FTPS_QUIT;
ftpSendCmd(ftpc,"QUIT");
} else if (ftpSetupLocalData(ftpc) < 0) { /* local failure */
UnregisterFtpFd(ftpc,ftpc->data); /* don't give up */
if (ftpc->data != -1) {
DEBUG1("ftpProcessReply: closing data %d\n",ftpc->data);
close(ftpc->data);
ftpc->data = -1;
}
ftpc->state = FTPS_READY; /* do next file */
goto redo;
} else { /* all ok locally */
ftpc->state = FTPS_TRANSFER;
}
} else if (ftpc->retcode == FTP_FILE_UNAVAILABLE || /* datacon */
ftpc->retcode == FTP_ACTION_NOT_TAKEN) { /* failed */
if (ftpc->port != -1) { /* minor */
DEBUG1("ftpProcessReply: closing port %d\n",ftpc->port);
close(ftpc->port); /* error */
ftpc->port = -1;
}
ftpAlert(ftpc); /* error msg */
ftpc->state = FTPS_READY; /* next file */
goto redo;
} else {
ftpHandleErrorThenQuit(ftpc); /* real error */
}
break;
case FTPS_TRANSFER:
/* Shouldn't get here since dataconn is separately managed */
DEBUG0("ftpProcessReply: called in state TRANSFER!\n");
break;
case FTPS_EOF:
/* Called when the dataconn is closed, for whatever reason */
if (!FTP_REPLY_OK(ftpc->retcode)) { /* dataconn closed error */
ftpAlert(ftpc);
}
ftpc->state = FTPS_READY;
goto redo;
case FTPS_QUIT:
if (ftpc->retcode != FTP_SERVICE_CLOSING) { /* error */
ftpAlert(ftpc);
}
ftpDone(ftpc); /* close, deallocate */
break;
case FTPS_ABORT:
if (ftpc->retcode == 552) { /* NIC-style abort */
ftpc->state = FTPS_ABORT; /* get another reply */
} else {
if (ftpc->retcode != FTP_DATA_CLOSE_ABORT) { /* 426 */
ftpAlert(ftpc); /* expected */
}
ftpc->state = FTPS_ABORT2; /* get real reply */
ftpInitReply(ftpc);
}
break;
case FTPS_ABORT2:
if (!FTP_REPLY_OK(ftpc->retcode)) { /* abort error */
ftpAlert(ftpc);
}
ftpCleanupDataConn(ftpc);
ftpc->state = FTPS_READY;
goto redo;
default:
fprintf(stderr,"ftpProcessReply: unknown state: %d\n",ftpc->state);
abort();
}
DEBUG0("ftpProcessReply: done\n");
}
/*
* This function just puts up an FTP error message.
*/
static void
ftpAlert(ftpc)
FtpContext *ftpc;
{
char buf[256],*msg;
int len;
msg = ftpc->reply+4; /* skip code */
len = strlen(msg);
if (len < 230) {
sprintf(buf,"FTP Error %d:\n %s",ftpc->retcode,msg);
} else {
sprintf(buf,"FTP Error %d: ...\n %.230s",ftpc->retcode,msg+len-230);
}
alert0(buf);
}
/*
* This prints an error message, then sends the QUIT command (or
* calls ftpDone() if the server shut down).
*/
static void
ftpHandleErrorThenQuit(ftpc)
FtpContext *ftpc;
{
ftpAlert(ftpc);
/* Don't bother with QUIT if remote host closing down */
if (ftpc->retcode == FTP_SERVICE_UNAVAILABLE) {
ftpDone(ftpc);
} else {
status0("Quitting...");
ftpc->state = FTPS_QUIT;
ftpSendCmd(ftpc,"QUIT");
}
}
/*
* This calls the done function (or ftpFreeContext() if none is defined
* for FTPC. All connections should pass through here to be cleaned up
* regardless of how they got here.
*/
static void
ftpDone(ftpc)
FtpContext *ftpc;
{
DEBUG1("ftpDone: ftpc=0x%x\n",ftpc);
if (ftpc->done != NULL) {
(*(ftpc->done))(ftpc);
} else {
ftpFreeContext(ftpc);
}
DEBUG0("ftpDone: done\n");
}
/* - - - - - - - - */
/* Functions called by ftpProcessReply(): */
static void
ftpSendType(ftpc)
FtpContext *ftpc;
{
char cmd[16];
DEBUG2("ftpSendType: ftpc=0x%x, type=%d\n",ftpc,ftpc->type);
ftpc->state = FTPS_TYPE;
switch (ftpc->type) {
case TYPE_A:
sprintf(cmd,"TYPE A");
break;
case TYPE_E:
sprintf(cmd,"TYPE E");
break;
case TYPE_I:
sprintf(cmd,"TYPE I");
break;
default:
sprintf(cmd,"TYPE L %d",(char *)ftpc->type);
}
status0(cmd);
ftpSendCmd(ftpc,cmd);
DEBUG0("ftpSendType: done\n");
}
static void
ftpSendNextCwd(ftpc)
FtpContext *ftpc;
{
char *slash,cmd[256];
DEBUG2("ftpSendNextCwd: ftpc=0x%x, wd=\"%s\"\n",ftpc,ftpc->wd);
if (*(ftpc->wd) == '/') { /* Leading slash treated specially... */
ftpc->state = FTPS_CWD;
ftpSendCmd(ftpc,"CWD /");
ftpc->wd += 1;
} else { /* Normal case */
if ((slash=index(ftpc->wd,'/')) != NULL) {
*slash = '\0';
}
sprintf(cmd,"CWD %s",ftpc->wd);
status0(cmd);
ftpc->state = FTPS_CWD;
ftpSendCmd(ftpc,cmd);
if (slash) {
ftpc->wd = slash+1;
} else {
/* set wd to end of string */
while (*(ftpc->wd) != '\0')
ftpc->wd += 1;
}
}
DEBUG0("ftpSendNextCwd: done\n");
}
/*
* Performs the PORT command. The new port is saved in f->port.
* Returns < 0 if some local error, otherwise 0 if we got to sending
* the command.
*/
static int
ftpSendPort(ftpc)
FtpContext *ftpc;
{
char cmd[64];
struct sockaddr_in addr;
int addrlen;
DEBUG1("ftpSendPort: ftpc=0x%lx\n",ftpc);
if ((ftpc->port=socket(AF_INET,SOCK_STREAM,0)) < 0) {
sysError("socket(ftpSendPort)");
DEBUG0("ftpSendPort: returning -1\n");
return(-1);
}
DEBUG1("ftpSendPort: socket() returned %d\n",ftpc->port);
addr = ftpc->saddr;
addr.sin_port = 0;
if (bind(ftpc->port,(struct sockaddr *)&addr,
sizeof(struct sockaddr_in)) < 0) {
sysError("bind(ftpSendPort)");
DEBUG1("ftpSendPort: closing port %d\n",ftpc->port);
close(ftpc->port);
DEBUG0("ftpSendPort: returning -1\n");
return(-1);
}
DEBUG0("ftpSendPort: bind() succeeded\n");
if (listen(ftpc->port,1) < 0) {
sysError("listen(ftpSendPort)");
DEBUG1("ftpSendPort: closing port %d\n",ftpc->port);
close(ftpc->port);
DEBUG0("ftpSendPort: returning -1\n");
return(-1);
}
DEBUG0("ftpSendPort: listen() succeeded\n");
addrlen = sizeof(struct sockaddr_in);
if (getsockname(ftpc->port,(struct sockaddr *)&addr,&addrlen) < 0) {
sysError("getsockname(ftpSendPort)");
DEBUG1("ftpSendPort: closing port %d\n",ftpc->port);
close(ftpc->port);
DEBUG0("ftpSendPort: returning -1\n");
return(-1);
}
DEBUG2("ftpSendPort: PORT address: %s; port: %d\n",
inet_ntoa(addr.sin_addr),addr.sin_port);
sprintf(cmd,"PORT %d,%d,%d,%d,%d,%d",
(int)((unsigned char *)&addr.sin_addr.s_addr)[0],
(int)((unsigned char *)&addr.sin_addr.s_addr)[1],
(int)((unsigned char *)&addr.sin_addr.s_addr)[2],
(int)((unsigned char *)&addr.sin_addr.s_addr)[3],
(int)((unsigned char *)&addr.sin_port)[0],
(int)((unsigned char *)&addr.sin_port)[1]);
status0(cmd);
ftpc->state = FTPS_PORT;
ftpSendCmd(ftpc,cmd);
DEBUG0("ftpSendPort: returning 0\n");
return(0);
}
/*
* After PORT is ok, send GET or PUT to open the dataconn.
*/
static void
ftpSendGetPut(ftpc)
FtpContext *ftpc;
{
char cmd[MAXPATHLEN];
DEBUG2("ftpSendGetPut: ftpc=0x%x, fcmd=%d\n",ftpc,ftpc->filecmd);
ftpc->state = FTPS_GETPUT;
switch (ftpc->filecmd) {
case FTP_GET:
sprintf(cmd,"RETR %s",ftpc->files[ftpc->this_file]);
break;
case FTP_PUT:
sprintf(cmd,"STOR %s",ftpc->files[ftpc->this_file]);
break;
}
status0(cmd);
ftpSendCmd(ftpc,cmd);
DEBUG0("ftpSendGetPut: done\n");
}
/*
* After GET/PUT, accepts the connection from the PORT and closes the
* the PORT. Returns the dataconn fd (also in ftpc->data).
*/
static int
ftpAcceptDataConn(ftpc)
FtpContext *ftpc;
{
int datacon,addrlen;
struct sockaddr_in addr;
DEBUG1("ftpAcceptDataConn: fcmd %d successful\n",ftpc->filecmd);
addrlen = sizeof(struct sockaddr_in);
if ((datacon=accept(ftpc->port,(struct sockaddr *)&addr,&addrlen)) < 0) {
sysError("accept(ftpAcceptDataConn)");
DEBUG0("ftpAcceptDataConn: returning -1\n");
return(-1);
}
DEBUG1("ftpAcceptDataConn: closing port %d\n",ftpc->port);
close(ftpc->port);
ftpc->port = -1;
DEBUG1("ftpAcceptDataConn: registering dataconn %d\n",datacon);
ftpc->data = datacon;
MAKE_NONBLOCKING(ftpc->data);
RegisterFtpFd(ftpc,ftpc->data,O_RDONLY,ftpDataCallback);
DEBUG1("ftpAcceptDataConn: returning dataconn = %d\n",datacon);
return(datacon);
}
/*
* Once the remote file is ready for transfer through the dataconn, this
* function sets up the local end. Returns -1 on error, otherwise 0.
*/
static int
ftpSetupLocalData(ftpc)
FtpContext *ftpc;
{
char filename[MAXPATHLEN];
DEBUG1("ftpSetupLocalData: ftpc=0x%x\n",ftpc);
if (ftpc->local_dir && *(ftpc->local_dir)) {
sprintf(filename,"%s/%s",ftpc->local_dir,ftpc->files[ftpc->this_file]);
} else {
strcpy(filename,ftpc->files[ftpc->this_file]);
}
DEBUG1("ftpSetupLocalData: opening \"%s\"\n",filename);
if (ftpc->filecmd == FTP_GET) {
ftpc->local_fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0644);
} else {
ftpc->local_fd = open(filename,O_RDONLY,0);
}
if (ftpc->local_fd < 0) {
sysError(filename);
DEBUG0("ftpSetupLocalData: returning -1\n");
return(-1);
}
DEBUG1("ftpSetupLocalData: got local fd = %d\n",ftpc->local_fd);
ftpc->num_bytes = 0;
DEBUG0("ftpSetupLocalData: returning 0\n");
return(0);
}
/* - - - - - - - - */
/* Data connection processing: */
/*
* This function is called whenever the dataconn is ready for read/write.
*/
static void
ftpDataCallback(ftpc)
FtpContext *ftpc;
{
DEBUG2("ftpDataCallback: ftpc=0x%x, fcmd=%d\n",ftpc,ftpc->filecmd);
switch (ftpc->filecmd) {
case FTP_GET:
ftpReadData(ftpc);
break;
case FTP_PUT:
ftpWriteData(ftpc);
break;
default:
fprintf(stderr,"ftpDataCallback: unknown cmd: %d\n",ftpc->filecmd);
abort();
}
DEBUG0("ftpDataCallback: done\n");
}
/*
* This function is called to read from the dataconn and write to the
* local_fd. On EOF, it sets the state field to FTPS_EOF and calls
* ftpInitReply() to continue the session. On error, it calls
* ftpSendAbort() and then ftpInitReply(). Otherwise the transfer
* is ongoing, so it doesn't do anything.
*/
static void
ftpReadData(ftpc)
FtpContext *ftpc;
{
char buf[BUFSIZ*4],*s,*t,msg[256];
int nread,i;
DEBUG0("ftpReadData: reading\n");
/* Read one chunk of stuff and write it locally (don't loop or
* X won't get a chance to dispatch).
*/
nread = read(ftpc->data,buf,sizeof(buf));
if (nread > 0) {
/* Strip CR's if necessary */
if (ftpc->type == TYPE_A && ftpc->stripcr) {
DEBUG0("ftpReadData: stripping <CR>\n");
for (s=t=buf,i=nread; i--; ++s) {
if (*s != '\r')
*t++ = *s;
}
}
/* Write locally */
if (write(ftpc->local_fd,buf,nread) != nread) {
/* Error when writing */
sysError("write(ftpReadData)");
ftpCleanupDataConn(ftpc);
ftpSendAbort(ftpc);
ftpInitReply(ftpc);
} else {
/* write was ok */
ftpc->num_bytes += nread;
DEBUG1("ftpReadData: total = %d\n",ftpc->num_bytes);
if (ftpc->this_size != 0) {
sprintf(msg,"%.200s: %d/%d",ftpc->files[ftpc->this_file],
(char *)(ftpc->num_bytes),(char *)(ftpc->this_size));
} else {
sprintf(msg,"%.200s: %d bytes",ftpc->files[ftpc->this_file],
(char *)(ftpc->num_bytes));
}
status0(msg);
}
} else if (nread < 0 && errno == ITWOULDBLOCK) {
/* Nothing more ready now, keep waiting */
DEBUG0("ftpReadData: dataconn would block\n");
} else if (nread < 0) {
/* Error when reading */
sysError("read(ftpReadData)");
DEBUG0("ftpReadData: error reading dataconn\n");
ftpCleanupDataConn(ftpc);
ftpSendAbort(ftpc);
ftpInitReply(ftpc);
} else if (nread == 0) {
/* We got EOF on remote file. */
DEBUG0("ftpReadData: EOF on dataconn\n");
ftpCleanupDataConn(ftpc);
if (ftpc->done1 != NULL)
(*(ftpc->done1))(ftpc);
ftpc->state = FTPS_EOF;
ftpInitReply(ftpc);
}
}
/*ARGSUSED*/
static void
ftpWriteData(ftpc)
FtpContext *ftpc;
{
/*EMPTY*/
}
static void
ftpCleanupDataConn(ftpc)
FtpContext *ftpc;
{
/* Note: We may have already unregistered, eg, in ftpAbort(). */
if (ftpc->state != FTPS_ABORT2)
UnregisterFtpFd(ftpc,ftpc->data);
if (ftpc->data != -1) {
DEBUG1("ftpCleanupDataConn: closing data %d\n",ftpc->data);
close(ftpc->data);
ftpc->data = -1;
}
if (ftpc->local_fd != -1) {
DEBUG1("ftpCleanupDataConn: closing local_fd %d\n",ftpc->local_fd);
close(ftpc->local_fd);
ftpc->local_fd = -1;
}
}
/* - - - - - - - - */
/* Sending commands to the server: */
/*
* Adds CRLF and calls to ftpStartSendingCmd().
*/
static void
ftpSendCmd(ftpc,cmd)
FtpContext *ftpc;
char *cmd;
{
char buf[MAXPATHLEN];
if (ftpc->trace) {
(*(ftpc->trace))(ftpc,1,cmd);
}
sprintf(buf,"%s\r\n",cmd);
ftpStartSendingCmd(ftpc,buf);
}
/*
* Handles abort processing by sending the Telnet abort sequence in the
* out-of-band stream of the ctrl conn and the ABOR command on the regular
* stream.
*/
static void
ftpSendAbort(ftpc)
FtpContext *ftpc;
{
char buf[8];
int sent;
DEBUG1("ftpSendAbort: ftpc=0x%lx\n",ftpc);
DEBUG0("ftpSendAbort: sending <IAC><IP><IAC> in OOB\n");
sprintf(buf,"%c%c%c",IAC,IP,IAC);
do {
sent = send(ftpc->ctrl,buf,3,MSG_OOB);
} while (sent == -1 && errno == EINTR);
if (sent != 3) {
sysError("send(ftpSendAbort)");
ftpDone(ftpc);
}
DEBUG0("ftpSendAbort: sending <DM>ABOR\n");
sprintf(buf,"%cABOR\r\n",DM);
do {
sent = write(ftpc->ctrl,buf,strlen(buf));
} while (sent == -1 && errno == EINTR);
if (sent != strlen(buf)) {
sysError("write(ftpSendAbort)");
ftpDone(ftpc);
}
ftpc->state = FTPS_ABORT;
ftpInitReply(ftpc);
status0("Aborting...");
DEBUG0("ftpSendAbort: done\n");
}
/*
* This function starts sending CMD to the server over the control
* connection. The writes can block, hence this has to be able to finish
* later, but since they usually don't block, we don't set up to retry
* unless we have to. If the command is completely sent, then we call
* ftpInitReply() otherwise we setup to finish by registering the ctrlconn
* for writing via callback ftpWriteCallback().
*/
static void
ftpStartSendingCmd(ftpc,cmd)
FtpContext *ftpc;
char *cmd;
{
int cmdlen,numsent;
DEBUG2("ftpStartSendingCmd: ftpc=0x%x: \"%s\"\n",ftpc,cmd);
cmdlen = strlen(cmd);
numsent = write(ftpc->ctrl,cmd,cmdlen);
if (numsent < 0 && errno != ITWOULDBLOCK) {
sysError("write(ftpStartSendingCmd)");
DEBUG0("ftpStartSendingCmd: error writing ctrlconn\n");
ftpDone(ftpc);
return;
} else if (numsent == cmdlen) {
DEBUG0("ftpStartSendingCmd: write all done\n");
ftpInitReply(ftpc);
return;
} else if (errno == ITWOULDBLOCK) {
/* Otherwise the write would block, nothing sent */
DEBUG0("ftpStartSendingCmd: write would block\n");
numsent = 0;
} else {
/* Incomplete write */
DEBUG1("ftpStartSendingCmd: write incomplete (%d bytes sent)\n",numsent);
}
/* Either way, if we get here there's something left do */
cmd += numsent;
if ((ftpc->cmd=malloc(strlen(cmd)+1)) == NULL) {
sysError("malloc(ftpStartSendingCmd)");
ftpDone(ftpc);
return;
}
strcpy(ftpc->cmd,cmd);
DEBUG1("ftpStartSendingCmd: pending: \"%s\"\n",ftpc->cmd);
RegisterFtpFd(ftpc,ftpc->ctrl,O_WRONLY,ftpWriteCallback);
}
/*
* This function is called when the ctrlconn is ready for writing.
* It sends as much as possible. If that completes the cmd, then it
* calls ftpInitReply(), otherwise updates the cmd with what's left
* and returns.
*/
static void
ftpWriteCallback(ftpc)
FtpContext *ftpc;
{
char *cmd;
int cmdlen,numsent;
cmd = ftpc->cmd;
DEBUG2("ftpWriteCallback: ftpc=0x%x: \"%s\"\n",ftpc,cmd);
cmdlen = strlen(ftpc->cmd);
numsent = write(ftpc->ctrl,cmd,cmdlen);
if (numsent < 0 && errno != ITWOULDBLOCK) {
sysError("write(ftpWriteCallback)");
DEBUG0("ftpWriteCallback: error writing ctrlconn\n");
ftpDone(ftpc);
return;
} else if (numsent == cmdlen) {
DEBUG0("ftpWriteCallback: write all done\n");
UnregisterFtpFd(ftpc,ftpc->ctrl);
free(ftpc->cmd);
ftpInitReply(ftpc);
return;
} else if (errno == ITWOULDBLOCK) {
/* Otherwise the write would block, nothing sent */
DEBUG0("ftpWriteCallback: write would block\n");
numsent = 0;
} else {
/* Incomplete write */
DEBUG1("ftpWriteCallback: write incomplete (%d bytes sent)\n",numsent);
}
/* Either way, if we get here there's something left do */
cmd += numsent;
free(ftpc->cmd);
if ((ftpc->cmd=malloc(strlen(cmd)+1)) == NULL) {
sysError("malloc(ftpWriteCallback)");
ftpDone(ftpc);
return;
}
strcpy(ftpc->cmd,cmd);
DEBUG1("ftpWriteCallback: pending: \"%s\"\n",ftpc->cmd);
}
/* - - - - - - - - */
/* Printing utilities for debugging */
#ifdef DEBUG
char *
ftpstatestr(state)
int state;
{
char buf[8];
switch (state) {
/* state */
case FTPS_OPEN: return("OPEN");
case FTPS_CONNECT: return("CONNECT");
case FTPS_CONNECTED: return("CONNECTED");
case FTPS_USER: return("USER");
case FTPS_PASS: return("PASS");
case FTPS_CWD: return("CWD");
case FTPS_TYPE: return("TYPE");
case FTPS_READY: return("READY");
case FTPS_PORT: return("PORT");
case FTPS_GETPUT: return("GETPUT");
case FTPS_TRANSFER: return("TRANSFER");
case FTPS_EOF: return("EOF");
case FTPS_QUIT: return("QUIT");
case FTPS_ABORT: return("ABORT");
case FTPS_ABORT2: return("ABORT2");
/* reply_state */
case FTPS_REPLY_CODE: return("CODE");
case FTPS_REPLY_CONT: return("CONT");
case FTPS_REPLY_LAST: return("LAST");
case FTPS_REPLY_MORE: return("MORE");
case FTPS_REPLY_CHCK: return("CHCK");
case FTPS_REPLY_IAC1: return("IAC1");
case FTPS_REPLY_IAC2: return("IAC2");
case FTPS_REPLY_IAC3: return("IAC3");
default: sprintf(buf,"?%d?",state);
return(buf);
}
}
#endif /* DEBUG */
/* - - - - - - - - */
/* Sample code for standalone client */
#ifdef STANDALONE
/* This is from Brendan... */
#include <sys/types.h> /* this may/will define FD_SET etc */
#ifdef u3b2
# include <sys/inet.h> /* THIS does FD_SET etc on AT&T 3b2s. */
#endif
fd_set readfds,writefds;
typedef void (*PF)();
PF funcs[FD_SETSIZE];
FtpContext *contexts[FD_SETSIZE];
void
RegisterFtpFd(ftpc,fd,flags,func)
FtpContext *ftpc;
int fd,flags;
void (*func)();
{
switch (flags) {
case O_RDONLY:
fprintf(stderr,"REGISTER fd %d, flags=RO\n",fd);
FD_SET(fd,&readfds);
FD_CLR(fd,&writefds);
break;
case O_WRONLY:
fprintf(stderr,"REGISTER fd %d, flags=WO\n",fd);
FD_CLR(fd,&readfds);
FD_SET(fd,&writefds);
break;
case O_RDWR:
fprintf(stderr,"REGISTER fd %d, flags=RW\n",fd);
FD_SET(fd,&readfds);
FD_SET(fd,&writefds);
break;
}
funcs[fd] = func;
contexts[fd] = ftpc;
}
/*ARGSUSED*/
void
UnregisterFtpFd(ftpc,fd)
FtpContext *ftpc;
int fd;
{
fprintf(stderr,"UNREGISTER fd %d\n",fd);
FD_CLR(fd,&readfds);
FD_CLR(fd,&writefds);
funcs[fd] = NULL;
contexts[fd] = NULL;
}
void
done(ftpc)
FtpContext *ftpc;
{
fprintf(stderr,"DONE!\n");
ftpFreeContext(ftpc);
exit(0);
}
void
done1(ftpc)
FtpContext *ftpc;
{
fprintf(stderr,"DONE1: \"%s\"\n",ftpc->files[ftpc->this_file]);
}
/*ARGSUSED*/
void
trace(ftpc,who,text)
FtpContext *ftpc;
int who; /* 0 => recvd, non-0 => sent */
char *text;
{
fprintf(stderr,"TRACE: ");
if (who) /* non-zero == us */
fprintf(stderr,"ftp> ");
fprintf(stderr,"%s",text);
if (*(text+strlen(text)-1) != '\n')
fprintf(stderr,"\n",text);
}
char *program;
main(argc,argv)
int argc;
char *argv[];
{
char *host,*cwd;
FtpContext *ftpc;
fd_set rfds,wfds;
int fd;
if (argc < 3) {
fprintf(stderr,"usage: %s host cwd files...\n",argv[0]);
exit(1);
}
argc -= 1;
program = *argv++;
argc -= 1;
host = *argv++;
argc -= 1;
cwd = *argv++;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
ftpc = ftpNewContext(host,"anonymous","ferguson@cs.rochester.edu",
cwd,TYPE_I,1,1,FTP_GET,argv,argc,trace,done1,done);
printf("Calling ftpStart...\n");
ftpStart(ftpc);
while (1) {
bcopy((char *)&readfds,(char *)&rfds,sizeof(fd_set));
bcopy((char *)&writefds,(char *)&wfds,sizeof(fd_set));
fprintf(stderr,"select(): R=");
for (fd=0; fd < FD_SETSIZE; fd++) {
if (FD_ISSET(fd,&rfds))
fprintf(stderr,"%d,",fd);
}
fprintf(stderr," W=");
for (fd=0; fd < FD_SETSIZE; fd++) {
if (FD_ISSET(fd,&wfds))
fprintf(stderr,"%d,",fd);
}
fprintf(stderr,"\n");
if (select(FD_SETSIZE,&rfds,&wfds,NULL,NULL) < 0) {
perror(program);
exit(1);
}
for (fd=0; fd < FD_SETSIZE; fd++) {
if (FD_ISSET(fd,&rfds) || FD_ISSET(fd,&wfds)) {
fprintf(stderr,"selected fd %d...\n",fd);
(*(funcs[fd]))(contexts[fd]);
}
}
}
}
void
sysError(s)
char *s;
{
extern int errno;
extern char *sys_errlist[];
fprintf(stderr,"SYSERR: %s: %s\n",s,sys_errlist[errno]);
}
void
alert0(s)
char *s;
{
fprintf(stderr,"ALERT: %s\n",s);
}
void
status0(s)
char *s;
{
fprintf(stderr,"STATUS: %s\n",s);
}
int
ftpPrompt(ftpc)
FtpContext *ftpc;
{
char c;
fprintf(stderr,"CONFIRM: %s %s [ynaq]",
ftpfilecmdstr(ftpc->filecmd),ftpc->files[ftpc->this_file]);
c = getchar();
if (c != '\n')
(void)getchar();
switch (c) {
case 'y': return(1);
case 'n': return(0);
case 'a': ftpc->prompt = 0;
return(1);
case 'q': ftpc->this_file = ftpc->num_files;
return(0);
default: return(1);
}
}
#endif /*STANDALONE*/